﻿namespace MetaProperties.Core
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;

    using MetaProperties.Framework;

    /// <summary>
    /// Defines a MetaProperty.
    /// </summary>
    public abstract class MetaProperty : IMetaProperty
    {
        /// <summary>
        /// The source container.
        /// </summary>
        private readonly IObservable container;

        /// <summary>
        /// The list of filters.
        /// </summary>
        private readonly List<IEventFilter> filters = new List<IEventFilter>();

        /// <summary>
        /// The total number of filters.
        /// </summary>
        private int filterCount;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetaProperty"/> class.
        /// </summary>
        /// <param name="container">The source container.</param>
        public MetaProperty(IObservable container)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }

            this.container = container;
        }

        /// <summary>
        /// Occurs when a property value is changing.
        /// </summary>
        public abstract event PropertyChangingEventHandler PropertyChanging;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public abstract event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public abstract event ValueChangedEventHandler UntypedValueChanged;

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        public abstract event ValueChangedEventHandler UntypedValueSet;

        /// <summary>
        /// Occurs when the Initialize method has been called.
        /// </summary>
        public event EventHandler Initialized;

        /// <summary>
        /// Gets the host of the property.
        /// </summary>
        /// <value>The host of the property.</value>
        public object Host
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the untyped value.
        /// </summary>
        /// <value>The untyped value.</value>
        public object UntypedValue
        {
            get
            {
                return this.container.UntypedValue;
            }
        }

        /// <summary>
        /// Gets the name of the property.
        /// </summary>
        /// <value>The name of the property.</value>
        public string PropertyName
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the type of the property.
        /// </summary>
        /// <value>The type of the property.</value>
        public abstract Type PropertyType
        {
            get;
        }

        /// <summary>
        /// Gets the filter count.
        /// </summary>
        /// <value>The filter count.</value>
        protected int FilterCount
        {
            get { return this.filterCount; }
        }

        /// <summary>
        /// Gets the list of filters.
        /// </summary>
        /// <value>The list of filters.</value>
        protected IList<IEventFilter> Filters
        {
            get { return this.filters; }
        }

        /// <summary>
        /// Initializes the MetaProperty.
        /// </summary>
        /// <param name="initializationInfo">The initialization information.</param>
        public virtual void Initialize(MetaPropertyConfiguration initializationInfo)
        {
            if (this.Host == null)
            {
                this.Host = initializationInfo.Host;
                this.PropertyName = initializationInfo.PropertyName;

                if (initializationInfo.Attributes != null)
                {
                    foreach (SetEventFilterAttribute a
                        in initializationInfo.Attributes.OfType<SetEventFilterAttribute>())
                    {
                        foreach (Type filterType in a.FilterTypes)
                        {
                            if (!typeof(IEventFilter).IsAssignableFrom(filterType))
                            {
                                throw new InvalidOperationException(
                                    "Type '"
                                    + filterType.FullName
                                    + "'is not derived from "
                                    + typeof(IEventFilter).Name
                                    + " when being added as a filter to property '"
                                    + initializationInfo.PropertyName
                                    + "' on type '"
                                    + initializationInfo.Host.GetType()
                                    + "'.");
                            }

                            IEventFilter filter = (IEventFilter)Activator.CreateInstance(filterType);
                            this.filters.Add(filter);
                        }
                    }
                }

                this.filterCount = this.filters.Count;

                foreach (IEventFilter filter in this.filters)
                {
                    filter.SetMetaPropertyConfiguration(initializationInfo);
                }

                var handler = this.Initialized;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }
    }
}
